package com.wxzhou.wangzhuan.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wxzhou.wangzhuan.constants.CacheNames;
import com.wxzhou.wangzhuan.entity.WithdrawSummary;
import com.wxzhou.wangzhuan.repository.WithdrawRepository;
import io.quarkus.redis.datasource.ReactiveRedisDataSource;
import io.quarkus.redis.datasource.keys.ReactiveKeyCommands;
import io.quarkus.redis.datasource.string.ReactiveStringCommands;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.UUID;

/**
 * 提现缓存服务类
 * 实现提现相关的缓存操作，包括分布式锁保护和一致性保证
 */
@ApplicationScoped
public class WithdrawCacheService {

    private static final Logger LOG = Logger.getLogger(WithdrawCacheService.class);

    @Inject
    ReactiveRedisDataSource redisDataSource;

    @Inject
    WithdrawRepository withdrawRepository;

    private ReactiveStringCommands<String, String> stringCommands;
    private ReactiveKeyCommands<String> keyCommands;

    // 分布式锁的过期时间（秒）
    private static final long LOCK_EXPIRE_TIME = 30;
    // 提现间隔要求（秒）
    private static final long WITHDRAW_INTERVAL_SECONDS = 60;

    // 初始化Redis命令对象
    @jakarta.annotation.PostConstruct
    void init() {
        this.stringCommands = redisDataSource.string(String.class, String.class);
        this.keyCommands = redisDataSource.key();
    }

    /**
     * 获取当日提现次数缓存键
     */
    private String getDailyCountKey(Long userId) {
        return CacheNames.KEY_WITHDRAW_DAILY_PREFIX + userId + CacheNames.KEY_WITHDRAW_DAILY_COUNT_SUFFIX;
    }

    private String getLockTaxAgreementKey(Long userId) {
        return CacheNames.KEY_LOCK_PREFIX + userId;
    }

    /**
     * 获取当日提现金额缓存键
     */
    private String getDailyAmountKey(Long userId) {
        return CacheNames.KEY_WITHDRAW_DAILY_PREFIX + userId + CacheNames.KEY_WITHDRAW_DAILY_AMOUNT_SUFFIX;
    }

    /**
     * 获取最后提现时间缓存键
     */
    private String getLastWithdrawTimeKey(Long userId) {
        return CacheNames.KEY_WITHDRAW_LAST_PREFIX + userId;
    }

    /**
     * 获取特定金额提现次数缓存键
     */
    private String getAmountCountKey(Long userId, BigDecimal amount) {
        // 将金额转换为字符串，去除小数点（因为金额单位是分）
        String amountStr = amount.toString().replace(".", "");
        return CacheNames.KEY_WITHDRAW_AMOUNT_PREFIX + userId + ":" + amountStr;
    }

    /**
     * 获取累计提现次数缓存键
     */
    private String getTotalCountKey(Long userId) {
        return CacheNames.KEY_WITHDRAW_TOTAL_PREFIX + CacheNames.KEY_WITHDRAW_TOTAL_COUNT_SUFFIX + userId;
    }

    /**
     * 获取累计提现金额缓存键
     */
    private String getTotalAmountKey(Long userId) {
        return CacheNames.KEY_WITHDRAW_TOTAL_PREFIX + CacheNames.KEY_WITHDRAW_TOTAL_AMOUNT_SUFFIX + userId;
    }

    /**
     * 获取分布式锁缓存键
     */
    private String getWithdrawLockKey(Long userId) {
        return CacheNames.KEY_WITHDRAW_LOCK_PREFIX + userId;
    }

    /**
     * 获取分布式锁缓存键,实名用!
     */
    private String getRealNameLockKey(Long userId) {
        return CacheNames.KEY_REALNAME_LOCK_PREFIX + userId;
    }

    /**
     * 获取绑定提现分布式锁缓存键
     */
    private String getBindWithdrawLockKey(Long userId, Integer paymentType) {
        return CacheNames.KEY_BIND_WITHDRAW_LOCK_PREFIX + userId + ":" + paymentType;
    }

    /**
     * 获取当日提现次数
     * 如果缓存不存在，则从数据库加载并缓存
     */
    public Uni<Integer> getDailyWithdrawalCount(Long userId) {
        String key = getDailyCountKey(userId);
        return stringCommands.get(key)
                .onItem().ifNull().switchTo(() -> loadDailyWithdrawalCountFromDb(userId))
                .onItem().transform(count -> count != null ? Integer.parseInt(count) : 0);
    }

    /**
     * 从数据库加载当日提现次数并缓存
     */
    private Uni<String> loadDailyWithdrawalCountFromDb(Long userId) {
        return withdrawRepository.countDailyWithdrawals(userId)
                .onItem().transform(count -> {
                    String key = getDailyCountKey(userId);
                    Duration ttl = getTtlUntilTomorrow();
                    return stringCommands.setex(key, ttl.getSeconds(), count.toString())
                            .replaceWith(count.toString());
                })
                .flatMap(uni -> uni);
    }

    /**
     * 获取当日提现总金额
     * 如果缓存不存在，则从数据库加载并缓存
     */
    public Uni<BigDecimal> getDailyWithdrawalAmount(Long userId) {
        String key = getDailyAmountKey(userId);
        return stringCommands.get(key)
                .onItem().ifNull().switchTo(() -> loadDailyWithdrawalAmountFromDb(userId))
                .onItem().transform(amount -> amount != null ? new BigDecimal(amount) : BigDecimal.ZERO);
    }

    /**
     * 从数据库加载当日提现总金额并缓存
     */
    private Uni<String> loadDailyWithdrawalAmountFromDb(Long userId) {
        return withdrawRepository.sumDailyWithdrawalAmount(userId)
                .onItem().transform(amount -> {
                    String key = getDailyAmountKey(userId);
                    Duration ttl = getTtlUntilTomorrow();
                    return stringCommands.setex(key, ttl.getSeconds(), amount.toString())
                            .replaceWith(amount.toString());
                })
                .flatMap(uni -> uni);
    }

    /**
     * 获取最后一次提现成功时间
     * 如果缓存不存在，则从数据库加载并缓存
     */
    public Uni<Long> getLastSuccessfulWithdrawalTime(Long userId) {
        String key = getLastWithdrawTimeKey(userId);
        return stringCommands.get(key)
                .onItem().ifNull().switchTo(() -> loadLastSuccessfulWithdrawalTimeFromDb(userId))
                .onItem().transform(time -> time != null ? Long.parseLong(time) : 0);
    }

    /**
     * 从数据库加载最后一次提现成功时间并缓存
     */
    private Uni<String> loadLastSuccessfulWithdrawalTimeFromDb(Long userId) {
        return withdrawRepository.getLastSuccessfulWithdrawalTime(userId)
                .onItem().transform(dateTime -> {
                    String key = getLastWithdrawTimeKey(userId);
                    long timestamp = dateTime != null ?
                            dateTime.toEpochSecond(ZoneOffset.of("+8")) : 0;
                    return stringCommands.set(key, String.valueOf(timestamp))
                            .replaceWith(String.valueOf(timestamp));
                })
                .flatMap(uni -> uni);
    }

    /**
     * 获取特定金额的提现次数
     * 如果缓存不存在，则从数据库加载并缓存
     */
    public Uni<Integer> getWithdrawalCountByAmount(Long userId, BigDecimal amount) {
        String key = getAmountCountKey(userId, amount);
        return stringCommands.get(key)
                .onItem().ifNull().switchTo(() -> loadWithdrawalCountByAmountFromDb(userId, amount))
                .onItem().transform(count -> count != null ? Integer.parseInt(count) : 0);
    }

    /**
     * 从数据库加载特定金额的提现次数并缓存
     */
    private Uni<String> loadWithdrawalCountByAmountFromDb(Long userId, BigDecimal amount) {
        return withdrawRepository.countWithdrawalsByAmount(userId, amount)
                .onItem().transform(count -> {
                    String key = getAmountCountKey(userId, amount);
                    return stringCommands.set(key, count.toString())
                            .replaceWith(count.toString());
                })
                .flatMap(uni -> uni);
    }

    /**
     * 获取累计提现次数
     * 如果缓存不存在，则从数据库加载并缓存
     */
    public Uni<Integer> getTotalWithdrawalCount(Long userId) {
        String key = getTotalCountKey(userId);
        return stringCommands.get(key)
                .onItem().ifNull().switchTo(() -> loadTotalWithdrawalCountFromDb(userId))
                .onItem().transform(count -> count != null ? Integer.parseInt(count) : 0);
    }

    /**
     * 从数据库加载累计提现次数并缓存
     */
    private Uni<String> loadTotalWithdrawalCountFromDb(Long userId) {
        return withdrawRepository.countTotalWithdrawals(userId)
                .onItem().transform(count -> {
                    String key = getTotalCountKey(userId);
                    return stringCommands.set(key, count.toString())
                            .replaceWith(count.toString());
                })
                .flatMap(uni -> uni);
    }

    /**
     * 获取累计提现总金额
     * 如果缓存不存在，则从数据库加载并缓存
     */
    public Uni<BigDecimal> getTotalWithdrawalAmount(Long userId) {
        String key = getTotalAmountKey(userId);
        return stringCommands.get(key)
                .onItem().ifNull().switchTo(() -> loadTotalWithdrawalAmountFromDb(userId))
                .onItem().transform(amount -> amount != null ? new BigDecimal(amount) : BigDecimal.ZERO);
    }

    /**
     * 从数据库加载累计提现总金额并缓存
     */
    private Uni<String> loadTotalWithdrawalAmountFromDb(Long userId) {
        return withdrawRepository.sumTotalWithdrawalAmount(userId)
                .onItem().transform(amount -> {
                    String key = getTotalAmountKey(userId);
                    return stringCommands.set(key, amount.toString())
                            .replaceWith(amount.toString());
                })
                .flatMap(uni -> uni);
    }

    /**
     * 通过用户先从redis中查找缓存,如果找不到,则从数据库中查找
     * 且只查找当日的记录
     *
     * @param userId
     * @return
     */
    public Uni<WithdrawSummary> findWithdrawSummaryByUser(Long userId) {
        // 用户统计面板缓存键
        String key = CacheNames.KEY_WITHDRAW_SUMMARY_PREFIX + userId;

        // 先检查Redis中是否存在缓存键
        return keyCommands.exists(key)
                .onItem().transformToUni(exists -> {
                    if (exists) {
                        // 如果键存在，获取缓存值
                        return stringCommands.get(key)
                                .onItem().transform(json -> {
                                    try {
                                        // 如果缓存内容为空，或者反序列化失败，从数据库加载
                                        if (json == null || json.isEmpty()) {
                                            return null;
                                        }
                                        // 使用Jackson的ObjectMapper解析JSON
                                        ObjectMapper mapper = new ObjectMapper();
                                        return mapper.readValue(json, WithdrawSummary.class);
                                    } catch (Exception e) {
                                        LOG.error("Failed to parse WithdrawSummary from cache", e);
                                        return null;
                                    }
                                });
                    } else {
                        // 如果键不存在，直接从数据库加载
                        return Uni.createFrom().nullItem();
                    }
                })
                .onItem().ifNull().switchTo(() -> loadWithdrawSummaryFromDbAndCache(userId, key));
    }

    /**
     * 从数据库加载用户提现统计信息并缓存
     */
    private Uni<WithdrawSummary> loadWithdrawSummaryFromDbAndCache(Long userId, String key) {
        return withdrawRepository.getWithdrawDashboardByUser(userId)
                .onItem().ifNull().continueWith(() -> {
                    // 如果数据库中也没有记录，创建一个空的WithdrawSummary对象
                    LOG.info("No withdraw summary found in database for user: " + userId);
                    return createEmptyWithdrawSummary(userId);
                })
                .onItem().call(summary -> {
                    try {
                        // 将WithdrawSummary对象序列化为JSON并设置到缓存
                        ObjectMapper mapper = new ObjectMapper();
                        String json = mapper.writeValueAsString(summary);
                        // 设置缓存过期时间为24小时
                        return stringCommands.setex(key, 24 * 60 * 60, json);
                    } catch (Exception e) {
                        LOG.error("Failed to cache WithdrawSummary", e);
                        return Uni.createFrom().voidItem();
                    }
                });
    }

    public Uni<WithdrawSummary> saveWithdrawSummary(Long userId) {
        String key = CacheNames.KEY_WITHDRAW_SUMMARY_PREFIX + userId;
        return loadWithdrawSummaryFromDbAndCache(userId, key);
    }

    /**
     * 创建一个空的WithdrawSummary对象
     */
    private WithdrawSummary createEmptyWithdrawSummary(Long userId) {
        WithdrawSummary summary = new WithdrawSummary();
        summary.setUserId(userId);
        summary.setDailyCount(0);
        summary.setDailySuccessCount(0);
        summary.setDailyPendingCount(0);
        summary.setDailyAmount(BigDecimal.ZERO);
        summary.setDailySuccessAmount(BigDecimal.ZERO);
        summary.setDailyPendingAmount(BigDecimal.ZERO);
        summary.setAmountItems(new HashMap<>());
        summary.setTotalAmount(BigDecimal.ZERO);
        summary.setLastTime(LocalDateTime.now());
        return summary;
    }

    /**
     * 获取当前用户的提现锁，锁存在，直接返回锁id，锁不存在，返回空
     *
     * @param userId
     * @return
     */
    public Uni<String> checkWithdrawLock(Long userId) {
        String lockKey = getWithdrawLockKey(userId);

        return stringCommands.get(lockKey)
                .onItem().transformToUni(currentLockValue -> {
                    if (currentLockValue == null) {
                        return Uni.createFrom().nullItem();
                    } else {
                        // 锁已存在
                        LOG.debugf("用户 %d 的提现锁已存在", userId);
                        return Uni.createFrom().item("1");
                    }
                });
    }


    /**
     * 获取分布式锁
     * 返回请求ID，用于后续验证
     */
    public Uni<String> acquireWithdrawLock(Long userId) {
        String lockKey = getWithdrawLockKey(userId);
        String requestId = UUID.randomUUID().toString();

        // 使用SET NX实现分布式锁
        return stringCommands.setnx(lockKey, requestId)
                .onItem().transformToUni(success -> {
                    if (Boolean.TRUE.equals(success)) {
                        return keyCommands.expire(lockKey, LOCK_EXPIRE_TIME).replaceWith(requestId);
                    }
                    return null;
                });
    }

    /**
     * 释放分布式锁
     * 简化实现，实际应用中可考虑使用其他方式确保安全性
     */
    public Uni<Void> releaseWithdrawLock(Long userId, String requestId) {
        String lockKey = getWithdrawLockKey(userId);

        // 先获取锁值进行验证
        return stringCommands.get(lockKey)
                .onItem().ifNotNull().call(currentRequestId -> {
                    // 如果是当前请求持有的锁，则释放
                    if (requestId.equals(currentRequestId)) {
                        return keyCommands.del(lockKey).replaceWithVoid();
                    }
                    return Uni.createFrom().voidItem();
                })
                .replaceWithVoid();
    }

    /**
     * 获取分布式锁，实名用
     * 返回请求ID，用于后续验证
     */
    public Uni<String> acquireRealNameLock(Long userId) {
        String lockKey = getRealNameLockKey(userId);
        String requestId = UUID.randomUUID().toString();

        // 使用SET NX实现分布式锁
        return stringCommands.setnx(lockKey, requestId)
                .onItem().transformToUni(success -> {
                    if (Boolean.TRUE.equals(success)) {
                        if (requestId.equals(requestId)) {
                            return keyCommands.expire(lockKey, LOCK_EXPIRE_TIME).replaceWith(requestId);
                        }
                    }
                    return null;
                });
    }

    /**
     * 获取绑定提现分布式锁
     * 返回请求ID，用于后续验证
     */
    public Uni<String> acquireBindWithdrawLock(Long userId, Integer paymentType) {
        String lockKey = getBindWithdrawLockKey(userId, paymentType);
        String requestId = UUID.randomUUID().toString();

        // 使用SET NX实现分布式锁，设置过期时间20秒
        return stringCommands.setnx(lockKey, requestId)
                .onItem().transformToUni(success -> {
                    if (Boolean.TRUE.equals(success)) {
                        if (requestId.equals(requestId)) {
                            return keyCommands.expire(lockKey, 20).replaceWith(requestId);
                        }
                    }
                    return null;
                });
    }

    /**
     * 解锁实名锁
     *
     * @param userId
     * @param requestId
     * @return
     */
    public Uni<Void> releaseRealNameLock(Long userId, String requestId) {
        String lockKey = getRealNameLockKey(userId);

        // 先获取锁值进行验证
        return stringCommands.get(lockKey)
                .onItem().ifNotNull().call(currentRequestId -> {
                    // 如果是当前请求持有的锁，则释放
                    if (requestId.equals(currentRequestId)) {
                        return keyCommands.del(lockKey).replaceWithVoid();
                    }
                    return Uni.createFrom().voidItem();
                })
                .replaceWithVoid();
    }

    /**
     * 释放绑定提现锁
     *
     * @param userId
     * @param paymentType
     * @param requestId
     * @return
     */
    public Uni<Void> releaseBindWithdrawLock(Long userId, Integer paymentType, String requestId) {
        String lockKey = getBindWithdrawLockKey(userId, paymentType);

        // 先获取锁值进行验证
        return stringCommands.get(lockKey)
                .onItem().ifNotNull().call(currentRequestId -> {
                    // 如果是当前请求持有的锁，则释放
                    if (requestId.equals(currentRequestId)) {
                        return keyCommands.del(lockKey).replaceWithVoid();
                    }
                    return Uni.createFrom().voidItem();
                })
                .replaceWithVoid();
    }


    /**
     * 将WithdrawSummary对象保存到Redis缓存
     *
     * @param summary WithdrawSummary对象
     * @param userId  用户ID
     * @return 操作结果的Uni
     */
    public Uni<Boolean> saveWithdrawSummaryToCache(WithdrawSummary summary, Long userId) {
        if (summary == null || userId == null) {
            LOG.warn("Cannot save null WithdrawSummary or userId to cache");
            return Uni.createFrom().item(false);
        }

        // 用户统计面板缓存键
        String key = CacheNames.KEY_WITHDRAW_SUMMARY_PREFIX + userId;

        try {
            // 将WithdrawSummary对象序列化为JSON
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(summary);

            // 设置缓存过期时间为24小时并返回操作结果
            return stringCommands.setex(key, 24 * 60 * 60, json)
                    .onItem().transform(status -> status != null)
                    .onFailure().recoverWithItem(error -> {
                        LOG.error("Failed to save WithdrawSummary to cache for user: " + userId, error);
                        return false;
                    });
        } catch (Exception e) {
            LOG.error("Failed to serialize WithdrawSummary to JSON", e);
            return Uni.createFrom().item(false);
        }
    }

    /**
     * 删除用户的WithdrawSummary缓存
     *
     * @param userId 用户ID
     * @return 操作结果的Uni
     */
    public Uni<Boolean> deleteWithdrawSummaryCache(Long userId) {
        if (userId == null) {
            return Uni.createFrom().item(false);
        }

        String key = CacheNames.KEY_WITHDRAW_SUMMARY_PREFIX + userId;
        return keyCommands.del(key)
                .onItem().transform(deletedCount -> deletedCount > 0)
                .onFailure().recoverWithItem(error -> {
                    LOG.error("Failed to delete WithdrawSummary cache for user: " + userId, error);
                    return false;
                });
    }

    /**
     * 检查提现间隔是否符合要求
     */
    public Uni<Boolean> checkWithdrawInterval(Long userId) {
        return getLastSuccessfulWithdrawalTime(userId)
                .onItem().transform(lastTime -> {
                    long currentTime = System.currentTimeMillis() / 1000;
                    // 如果最后提现时间为0，说明用户从未提现过，允许提现
                    if (lastTime == 0) {
                        return true;
                    }
                    // 检查两次提现间隔是否大于要求的时间
                    return (currentTime - lastTime) >= WITHDRAW_INTERVAL_SECONDS;
                });
    }

    /**
     * 更新当日提现统计数据
     */
    public Uni<Void> updateDailyWithdrawalStats(Long userId, BigDecimal amount) {
        // 增加当日提现次数
        Uni<Void> incrementCount = stringCommands.incr(getDailyCountKey(userId)).replaceWithVoid();

        // 增加当日提现金额
        Uni<Void> updateAmount = getDailyWithdrawalAmount(userId)
                .onItem().transform(currentAmount -> {
                    BigDecimal newAmount = currentAmount.add(amount);
                    String key = getDailyAmountKey(userId);
                    Duration ttl = getTtlUntilTomorrow();
                    return stringCommands.setex(key, ttl.getSeconds(), newAmount.toString())
                            .replaceWithVoid();
                })
                .flatMap(uni -> uni);

        // 同时更新最后提现时间
        Uni<Void> updateLastTime = stringCommands.set(getLastWithdrawTimeKey(userId),
                        String.valueOf(System.currentTimeMillis() / 1000))
                .replaceWithVoid();

        // 组合所有更新操作
        return Uni.combine().all().unis(incrementCount, updateAmount, updateLastTime)
                .discardItems();
    }

    /**
     * 更新特定金额提现统计
     */
    public Uni<Void> updateAmountWithdrawalStats(Long userId, BigDecimal amount) {
        return stringCommands.incr(getAmountCountKey(userId, amount)).replaceWithVoid();
    }

    /**
     * 更新累计提现统计数据
     */
    public Uni<Void> updateTotalWithdrawalStats(Long userId, BigDecimal amount) {
        // 增加累计提现次数
        Uni<Void> incrementCount = stringCommands.incr(getTotalCountKey(userId)).replaceWithVoid();

        // 增加累计提现金额
        Uni<Void> updateAmount = getTotalWithdrawalAmount(userId)
                .onItem().transform(currentAmount -> {
                    BigDecimal newAmount = currentAmount.add(amount);
                    return stringCommands.set(getTotalAmountKey(userId), newAmount.toString())
                            .replaceWithVoid();
                })
                .flatMap(uni -> uni);

        // 组合所有更新操作
        return Uni.combine().all().unis(incrementCount, updateAmount)
                .discardItems();
    }

    /**
     * 根据用户获取是否有锁定用户协议的键,如果有,则返回true
     *
     * @param userId
     * @return
     */
    public Uni<Boolean> getTaxAgreement(Long userId) {
        return keyCommands.exists(getLockTaxAgreementKey(userId));
    }


    //删除
    public Uni<Boolean> delTaxAgreement(Long userId) {
        return keyCommands.del(getLockTaxAgreementKey(userId)).onItem().transform(integer -> true).onFailure().recoverWithItem(throwable -> true);
    }

    /**
     * 清理用户的提现缓存
     * 用于确保缓存一致性
     */
    public Uni<Void> clearUserWithdrawCache(Long userId) {
        String[] keys = {
                getDailyCountKey(userId),
                getDailyAmountKey(userId),
                getLastWithdrawTimeKey(userId),
                getTotalCountKey(userId),
                getTotalAmountKey(userId)
        };
        return keyCommands.del(keys).replaceWithVoid();
    }

    /**
     * 获取距离次日凌晨的时间间隔
     */
    private Duration getTtlUntilTomorrow() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime tomorrow = now.toLocalDate().plusDays(1).atStartOfDay().plusMinutes(5);
        return Duration.between(now, tomorrow);
    }
}